﻿using System;
using System.Collections.Generic;
using System.Threading;

namespace Autorun
{
    [Serializable]
    public sealed class ServiceManagement : MarshalByRefObject
    {
        #region "Singleton Design-Pattern"
        private static ServiceManagement _instance = new ServiceManagement();

        public static ServiceManagement Instance
        {
            get { return _instance; }
        }

        private ServiceManagement()
        {
            sysRunningTasks = new SortedList<string, TaskContext>();
        }
        #endregion

        #region "Private members"
        private Thread sysThread;
        private ServiceStatuses sysStatus;
        private bool sysStopSignal;

        private SortedList<string, TaskContext> sysRunningTasks;
        #endregion

        #region "Events"
        public event StartEventHandler OnStart;
        public event StopEventHandler OnStop;
        public event NotifyEventHandler OnNotify;
        #endregion

        #region "Properties"
        public ServiceStatuses Status
        {
            get { return sysStatus; }
        }

        public int RunningTaskCount
        {
            get { return sysRunningTasks.Count; }
        }

        public SortedList<string, TaskContext> RunningTasks
        {
            get
            {
                SortedList<string, TaskContext> rc;

                rc = new SortedList<string, TaskContext>();
                lock (this)
                {
                    foreach (TaskContext item in sysRunningTasks.Values)
                        rc.Add(item.Key, item);
                    return rc;

                    //return sysRunningTasks.Values;
                }
            }
        }
        #endregion

        #region "Methods"
        public void Start()
        {
            string logMsg;

            switch (sysStatus)
            {
                case ServiceStatuses.Stopped:
                    // === Build the planning ==========================================
                    ScheduleManagement.Instance.BuildPlanning();

                    MonitorManagement.Instance.Start();

                    // === Start the thread ============================================
                    sysThread = new Thread(DoTask);
                    sysStopSignal = false; // Reset any previous stop signal
                    sysThread.Start();
                    // Normally it should be in DoTask, but the status will not be set
                    // on time when the GUI will refresh its information
                    sysStatus = ServiceStatuses.Running;

                    logMsg = string.Format("The Service thread {0} started", sysThread.Name);
                    LogManagement.Instance.SendServiceStatus(SeverityLevels.Info, sysStatus, logMsg);
                    //LogManagement.Instance.AddEntry(SeverityLevels.Info, logMsg, null);

                    break;
                default:
                    logMsg = string.Format("The Service thread {0} has an unmanaged status: {1}", sysThread.Name, sysStatus.ToString());
                    LogManagement.Instance.SendServiceStatus(SeverityLevels.Error, sysStatus, logMsg);
                    //LogManagement.Instance.AddEntry(SeverityLevels.Error, logMsg, null);

                    throw new Exception(logMsg);
            }
        }

        public void Stop()
        {
            string logMsg;

            if (sysThread != null)
            {
                sysStopSignal = true;
                sysThread.Join(30000);
                if (sysStatus == ServiceStatuses.Running)
                {
                    sysThread.Abort();
                    sysStatus = ServiceStatuses.Stopped;
                    sysStopSignal = false;
                }

                logMsg = "The service thread has received a stop signal and has been aborted.";
                if (OnStop != null) OnStop(this, new StopEventArgs(int.MinValue, new Exception(logMsg)));
                LogManagement.Instance.SendServiceStatus(SeverityLevels.Warning, sysStatus, logMsg);
                //LogManagement.Instance.AddEntry(SeverityLevels.Warning, logMsg, null);

                sysThread = null;
            }

            MonitorManagement.Instance.Stop();
        }

        private void DoTask()
        {
            TaskContext context;

            // =====================================================================
            // === Pre-Process                                                   ===
            // =====================================================================
            Thread.CurrentThread.Name = this.ToString();
            sysStatus = ServiceStatuses.Running;
            if (OnStart != null) OnStart(this, new StartEventArgs());

            // =====================================================================
            // === Process                                                       ===
            // =====================================================================
            while (sysStopSignal == false)
            {
                // === Build the planning, if the day changed ========================
                ScheduleManagement.Instance.BuildPlanning();

                // === Start a new task ==============================================
                if (ScheduleManagement.Instance.WaitingTasks.Count != 0)
                    if (sysRunningTasks.Count < ConfigurationManagement.Instance.ThreadPoolSize)
                        while ((sysStopSignal == false) && (ScheduleManagement.Instance.WaitingTasks.Count != 0) && (sysRunningTasks.Count < ConfigurationManagement.Instance.ThreadPoolSize))
                        {
                            context = ScheduleManagement.Instance.WaitingTasks.Values[0];

                            if (context.PlannedOn.CompareTo(DateTime.Now) == -1)
                            {
                                ScheduleManagement.Instance.WaitingTasks.RemoveAt(0);
                                context.LoadTask();
                                if (context.Task != null)
                                {
                                    context.Task.OnStart += new StartEventHandler(Task_OnStart);
                                    context.Task.OnStop += new StopEventHandler(Task_OnStop);
                                    context.Task.OnNotify += new NotifyEventHandler(Task_OnNotify);
                                    lock (this)
                                        sysRunningTasks.Add(context.Key, context);
                                    context.Task.Start(context);
                                }
                            }
                            else
                                Thread.Sleep(TimeSpan.FromSeconds(ConfigurationManagement.Instance.SleepTime));
                        }

                Thread.Sleep(TimeSpan.FromSeconds(ConfigurationManagement.Instance.SleepTime));
            }

            // =====================================================================
            // === Post-Process                                                  ===
            // =====================================================================
            // Terminate the running tasks
            lock (sysRunningTasks)
            {
                foreach (TaskContext item in sysRunningTasks.Values)
                    if (item != null) item.Task.Stop();
            }
            while (ScheduleManagement.Instance.WaitingTasksOnShutdown.Count != 0)
            {
                context = ScheduleManagement.Instance.WaitingTasksOnShutdown.Dequeue();
                context.LoadTask();
                context.Task.OnStart += new StartEventHandler(Task_OnStart);
                context.Task.OnStop += new StopEventHandler(Task_OnStop);
                context.Task.OnNotify += new NotifyEventHandler(Task_OnNotify);
                lock (this)
                    sysRunningTasks.Add(context.Key, context);
                context.Task.Start(context);
            }

            // Reset the scheduler
            ScheduleManagement.Instance.ResetPlanning();
            sysRunningTasks.Clear();
        }

        public void KillTask(string key)
        {
            lock (this)
            {
                if (sysRunningTasks.ContainsKey(key) == true)
                    sysRunningTasks[key].Task.Stop();
            }

        }
        #endregion

        #region "TaskBase Event Handlers"
        void Task_OnNotify(object sender, NotifyEventArgs e)
        {
            TaskContext context;

            context = ((TaskBase)sender).Context;
            LogManagement.Instance.AddEntry(((TaskBase)sender).Context, e.Severity, e.Message, null);

            if (OnNotify != null)
                OnNotify(sender, e);
        }

        void Task_OnStop(object sender, StopEventArgs e)
        {
            TaskContext context;

            // === New task to be chained? =========================================
            if (((TaskBase)sender).Context.NextTaskId != 0)
            {
                context = new TaskContext(((TaskBase)sender).Context);
                ScheduleManagement.Instance.PlanTask(context);
                LogManagement.Instance.AddEntry(((TaskBase)sender).Context, SeverityLevels.Info, string.Format("Chaining with new task (Key: {0})", context.Key), null);
            }

            // === Clean up the task ===============================================
            if (sysStopSignal == false)
                lock (this)
                    sysRunningTasks.Remove(((TaskBase)sender).Context.Key);

            //LogManagement.Instance.AddEntry(((TaskBase)sender).Context, (e.ExitCode == int.MinValue ? SeverityLevels.Error : SeverityLevels.Verbose), string.Format("Task #{0} stopped with exit code {1}", ((TaskBase)sender).Context.Key, e.ExitCode), e.Error);
            LogManagement.Instance.SendTaskReport(((TaskBase)sender).Context);
            if (OnNotify != null) OnNotify(sender, new NotifyEventArgs(SeverityLevels.Info, string.Format("Task {0} finished", sender)));
        }

        void Task_OnStart(object sender, StartEventArgs e)
        {
            LogManagement.Instance.AddEntry(((TaskBase)sender).Context, SeverityLevels.Verbose, string.Format("Task #{0} started", ((TaskBase)sender).Context.Key), null);
            if (OnNotify != null) OnNotify(sender, new NotifyEventArgs(SeverityLevels.Info, string.Format("Task {0} started", sender)));
        }
        #endregion
    }
}
